﻿using UnityEngine;
using System.Collections;
using System.Text;
using System.Collections.Generic;

public class GeneticAlgorithm {
    private List<Gene> population;

    private int nextId = 1;

    public int MaxGenerations = 10;

    public GeneticAlgorithm(int size, int MinSystemLength, int MaxSystemLength)
    {
        population = new List<Gene>();       

        for (int i = 0; i < size; i++)
        {
            Gene gene = new Gene(nextId++);
            while (!(gene.GenoType.System.Length > MinSystemLength && gene.GenoType.System.Length < MaxSystemLength))
            {
                gene = new Gene(nextId++);
            }
            population.Add(gene);
        }
    }

    public GeneticAlgorithm()
    {
        population = new List<Gene>();
    }

    public int Size()
    {
        return population.Count;
    }

    public Gene GetGene(int index)
    {
        return population[index];
    }

    public void AddGene(Gene gene)
    {
        population.Add(gene);
    }

    public Gene this[int i]
    {
        get { return population[i]; }
        set { population[i] = value; }
    }

    /// <summary>
    /// Evaluates a generation. Each gene should be expressed in it's phenotype
    /// and evaluated in game.
    /// </summary>
    public void EvaluateGeneration()
    {
        for (int i = 0; i < population.Count; i++)
        {
            Gene g = population[i];
            float fitness = 0;
            // Add fitness evalution from phenotype
            // Do simulation stuff here
            g.CreatePhenoType();
            // Get fitness from phenotype
            g.Fitness = fitness;
        }
    }

    public List<Gene> GetSortedGenotypes()
    {
        population.Sort((a, b) => a.Fitness.CompareTo(b.Fitness));
        return population;
    }

    public float AverageFitness()
    {
        population.Sort((a, b) => a.Fitness.CompareTo(b.Fitness));
        int startPoint = population.Count / 5 * 2;

        float average = 0.0f;
        for (int i = startPoint; i < population.Count; i++)
        {
            average += population[i].Fitness;
        }

        average /= population.Count - startPoint;
        return average;
    }

    public void NextGeneration()
    {
        population.Sort((a, b) => a.Fitness.CompareTo(b.Fitness));
        int replacements = population.Count / 5 * 2; // 20 % elitism
        
        // Remove the worst genes
        population.RemoveRange(0, replacements);

        population.Reverse();
        float mutateProb = 0.3f;
        for (int i = 0; i < replacements; i++)
        {
            //Gene[] children = population[i].Reproduce(population[i + 1]);
            //population.AddRange(children);

            //if (Random.Range(0, 1.0f) < mutateProb)
            //{
            //    children[0].Mutate();
            //}
            //if (Random.Range(0, 1.0f) < mutateProb)
            //{
            //    children[1].Mutate();
            //}


            // Take replacement best individuals and copy and mutate them
            Gene gene = new Gene(population[i].GenoType, nextId++, population[i].Id);
            gene.Mutate();     
            gene.Prepare();

            if (gene.GenoType.System.Length < Manager.Instance.MinSystemLength || gene.GenoType.System.Length > Manager.Instance.MaxSystemLength)
            {
                for (int j = 0; j < 10; j++)
                {
                    gene = new Gene(population[i].GenoType, nextId++, population[i].Id);
                    gene.Mutate();
                    gene.Prepare();
                    if (gene.GenoType.System.Length > Manager.Instance.MinSystemLength && gene.GenoType.System.Length < Manager.Instance.MaxSystemLength)
                        break;
                }
            }

            population.Add(gene);
        }
    }

    //public void RunEvolution(int generations)
    //{
    //    GeneticAlgorithm ga = new GeneticAlgorithm(10);

    //    int gen = 0;

    //    ga.EvaluateGeneration();
    //    ga.NextGeneration();

    //    while (true)
    //    {
    //        ga.EvaluateGeneration();

    //        float avgFitness = 0f;
    //        float maxFitness = float.NegativeInfinity, minFitness = float.PositiveInfinity;
    //        string best = "", worst = "";

    //        for (int i = 0; i < ga.Size(); i++)
    //        {
    //            float curFitness = ga.GetGene(i).Fitness;
    //            avgFitness += curFitness;
    //            if (curFitness < minFitness)
    //            {
    //                minFitness = curFitness;
    //                worst = ga.GetGene(i).GenoType.System;
    //            }
    //            if (curFitness > maxFitness)
    //            {
    //                maxFitness = curFitness;
    //                best = ga.GetGene(i).GenoType.System;
    //            }
    //        }
    //        avgFitness /= ga.Size();
    //        string output = "Generation: " + gen;
    //        output += "\t AvgFitness: " + avgFitness;
    //        output += "\t MinFitness: " + minFitness + " (" + worst + ")";
    //        output += "\t MaxFitness: " + maxFitness + " (" + best + ")";

    //        //print(output);

    //        ga.NextGeneration();
    //        gen++;
    //        if (gen > MaxGenerations)
    //        {
    //            break;
    //        }
    //    }
    //}
}